﻿using System.IO;
using System.Text;
using System.Xml;
using System.Xml.Schema;
using System.Xml.Serialization;

namespace CodeContrib
{
	public static class XmlSerializationExtensions
	{
		public static Encoding DefaultEncoding { get; set; }

		static XmlSerializationExtensions()
		{
			DefaultEncoding = Encoding.UTF8;
		}

		public static T DeserializeXml<T>(this string xml) where T : class, new()
		{
			return DeserializeXml<T>(xml, DefaultEncoding, null);
		}

		public static T DeserializeXml<T>(this string xml, XmlSchemaSet schemas) where T : class, new()
		{
			return DeserializeXml<T>(xml, DefaultEncoding, schemas);
		}

		public static T DeserializeXml<T>(this string xml, Encoding encoding) where T : class, new()
		{
			return DeserializeXml<T>(xml, encoding, null);
		}

		public static T DeserializeXml<T>(this string xml, Encoding encoding, XmlSchemaSet schemas) where T : class, new()
		{
			using (var stream = new MemoryStream(encoding.GetBytes(xml)))
			{
				var settings = new XmlReaderSettings();

				if (schemas != null)
				{
					settings.Schemas.Add(schemas);
					settings.ValidationType = ValidationType.Schema;
				}

				settings.ValidationFlags = XmlSchemaValidationFlags.ReportValidationWarnings;
				settings.ValidationEventHandler += ValidationEventHandler;

				var reader = XmlReader.Create(stream, settings);

				var serializer = new XmlSerializer(typeof (T));

				return (T)serializer.Deserialize(reader);
			}
		}

		public static string SerializeXml<T>(this T obj) where T : class, new()
		{
			return SerializeXml(obj, DefaultEncoding, null);
		}

		public static string SerializeXml<T>(this T obj, Encoding encoding) where T : class, new()
		{
			return SerializeXml(obj, encoding, null);
		}

		public static string SerializeXml<T>(this T obj, string defaultNamespace) where T : class, new()
		{
			return SerializeXml(obj, DefaultEncoding, defaultNamespace);
		}

		public static string SerializeXml<T>(this T obj, Encoding encoding, string defaultNamespace) where T : class, new()
		{
			var xml = new StringBuilder();

			using (var writer = new XmlTextWriter(new StringWriterWithEncoding(xml, encoding)))
			{
				var rootAttributes = obj.GetType().GetCustomAttributes(typeof (XmlRootAttribute), false);

				if (string.IsNullOrEmpty(defaultNamespace)
				    && rootAttributes.Length == 1)
				{
					var rootAttribute = (XmlRootAttribute)rootAttributes[0];

					defaultNamespace = rootAttribute.Namespace;
				}

				var serializerNamespaces = new XmlSerializerNamespaces();
				serializerNamespaces.Add(string.Empty, defaultNamespace);

				var serializer = new XmlSerializer(obj.GetType(), defaultNamespace);

				serializer.Serialize(writer, obj, serializerNamespaces);
			}

			return xml.ToString();
		}

		private static void ValidationEventHandler(object sender, ValidationEventArgs e)
		{
			throw e.Exception;
		}
	}
}